package com.spzx.product.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.spzx.common.core.utils.uuid.UUID;
import com.spzx.product.domain.ProductSku;
import com.spzx.product.mapper.ProductSkuMapper;
import com.spzx.product.query.SkuQuery;
import com.spzx.product.service.ProductSkuService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.spzx.product.vo.ProductSkuVo;
import com.spzx.product.vo.SkuPriceVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 商品sku 服务实现类
 * </p>
 *
 * @author atguigu
 * @since 2025-04-28
 */
@Service
@Slf4j
public class ProductSkuServiceImpl extends ServiceImpl<ProductSkuMapper, ProductSku> implements ProductSkuService {

    @Autowired
    private ProductSkuMapper productSkuMapper;
    @Autowired
    private RedisTemplate redisTemplate;
    @Override
    public List<ProductSkuVo> getProductSkuList() {
        List<ProductSkuVo> list= productSkuMapper.getProductSkuList();
        return list;
    }

    @Override
    public List<ProductSkuVo> getPageSkuList(SkuQuery skuQuery) {
        List<ProductSkuVo> list= productSkuMapper.getPageSkuList(skuQuery);
        return list;
    }

    @Override
    public SkuPriceVo getSkuPrice(Long skuId) {
        LambdaQueryWrapper<ProductSku> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(ProductSku::getId,skuId);
        wrapper.select(ProductSku::getSalePrice,ProductSku::getMarketPrice);
        ProductSku productSku = baseMapper.selectOne(wrapper);
        SkuPriceVo skuPriceVo = new SkuPriceVo();
        skuPriceVo.setMarketPrice(productSku.getMarketPrice());
        skuPriceVo.setSalePrice(productSku.getSalePrice());
        skuPriceVo.setSkuId(skuId);
        return skuPriceVo;
    }

    @Override
    public Map<String, Long> getSkuSpecValueMap(Long productId) {
        LambdaQueryWrapper<ProductSku> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(ProductSku::getProductId,productId);
        wrapper.select(ProductSku::getSkuSpec,ProductSku::getId);
        List<ProductSku> list = baseMapper.selectList(wrapper);
        //创建一个map存储数据
        Map<String, Long> stringLongHashMap = new HashMap<>();
        //遍历集合将数据存储到map中
        for (ProductSku productSku : list) {
            stringLongHashMap.put(productSku.getSkuSpec(),productSku.getId());
        }
        return stringLongHashMap;
    }

    @Override
    public ProductSku getProductSkuById(Long skuId) {
        //先设置一个redis中缓存数据的key 形式:sku:info:skuId,可以用来存也可以用来取
        String dataKey = "sku:info" + skuId;
        //判断一下redis中是否存在这个key,存在就取,不存在就从数据库中查
        ProductSku productSku= (ProductSku) redisTemplate.opsForValue().get(dataKey);
        if(productSku!=null){
            return productSku;
        }
        //尝试获取分布式锁,防止缓存穿透,导致大量请求直接访问数据库,导致数据库宕机
        String lockName =dataKey + ":lock";
        //为分布式锁添加唯一标识,防止其他线程获取锁
        String lockValue = UUID.randomUUID().toString();
        //尝试获取锁
        Boolean absent = redisTemplate.opsForValue().setIfAbsent(lockName, lockValue, 5, TimeUnit.SECONDS);
        if(absent){
            try {
                //获取锁成功后就让这一个线程去数据库查询数据然后带到缓存中
                productSku = getById(skuId);
                //将查询到的数据放到缓存中,并设置过期时间(随机,防止缓存雪崩)
                int ttl = 36000 + new Random().nextInt(600);
                redisTemplate.opsForValue().set(dataKey,productSku,ttl,TimeUnit.SECONDS);
            } finally {
                //释放锁
                String script ="if redis.call('get', KEYS[1]) == ARGV[1] then\n" +
                        "    return redis.call('del', KEYS[1])\n" +
                        "else\n" +
                        "    return 0\n" +
                        "end";
                redisTemplate.execute(new DefaultRedisScript(script,Boolean.class),Arrays.asList(lockName),lockValue);

            }
        }else {
            //获取所失败就自旋等待
            log.error("获取分布式锁失败:{},等待自旋",Thread.currentThread().getName());
             return this.getProductSkuById(skuId);
        }
         return null;
    }
}
